﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Drawing;
using System.Drawing.Imaging;

namespace LowLevelGraphics.SeamCarving
{
    /// <summary>
    /// Standard as a seam function
    /// </summary>
    public sealed class Standard : SeamFunction
    {
        /// <summary>
        /// Standard
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return "Standard";
        }

        /// <summary>
        /// Computes all seams.
        /// </summary>
        /// <param name="direction">The direction.</param>
        /// <param name="size">The size.</param>
        public override void ComputeAllSeams(Direction direction, Size size)
        {
            if (direction == Direction.Vertical)
            {
                verticalSeams = new Seam[size.Width];
                // Iterate through pixels within specified range
                for (int index = 0; index < size.Width; index++)
                {
                    verticalSeams[index] = ComputeSeam(Direction.Vertical,
                    index, EnergyFunction, EnergyBias, size);
                }
            }
            else if (direction == Direction.Horizontal)
            {
                horizontalSeams = new Seam[size.Height];
                // Iterate through pixels within specified range
                for (int index = 0; index < size.Height; index++)
                {
                    horizontalSeams[index] = ComputeSeam(Direction.Horizontal,
                    index, EnergyFunction, EnergyBias, size);
                }
            }
        }

        /// <summary>
        /// Re-compute seams that are affected by the "width" of the carved seam
        /// </summary>
        /// <param name="seam">The seam.</param>
        /// <param name="size">The size.</param>
        public override void RecomputeSeams(Seam seam, Size size)
        {
            int fromPixel = seam.Pixel - seam.FromPixel;
            int toPixel = seam.Pixel + seam.ToPixel;
            // Shift all seams beyond the affected region to the left
            int dstIndex = seam.Pixel;
            int srcIndex = dstIndex + 1;
            int maxIndex = 0;
            if (seam.Direction == Direction.Vertical)
                maxIndex = size.Width;
            else if (seam.Direction == Direction.Horizontal)
                maxIndex = size.Height;       // Compute the affected region

            for (dstIndex = seam.Pixel; dstIndex < maxIndex; dstIndex++)
            {
                // Depends on direction
                if (seam.Direction == Direction.Vertical)
                {
                    verticalSeams[dstIndex] = verticalSeams[srcIndex];
                    verticalSeams[dstIndex].Pixel--; //Shift the reference pixel
                }
                else
                {
                    horizontalSeams[dstIndex] = horizontalSeams[srcIndex];
                    horizontalSeams[dstIndex].Pixel--; //Shift the reference pixel
                }
                srcIndex++;
            }

            // TODO: Somewhere in this method, we need to subtract energy from the total calculations
            // (since we just removed a seam of pixels)
            
            // Set last seam to null
            if (seam.Direction == Direction.Vertical)
                verticalSeams[dstIndex] = null;
            else if (seam.Direction == Direction.Horizontal)
                horizontalSeams[dstIndex] = null;
            // Iterate through all seams
            int recomputedSeamCount = 0;
            int fromPixelCheck = 0;
            int toPixelCheck = 0;
            int maxCount = 0;
            if (seam.Direction == Direction.Vertical)
                maxCount = size.Width;
            else
                maxCount = size.Height;

            for (int index = 0; index < maxCount; index++)
            {
                // Get the right kind of seam
                Seam checkSeam = null;
                if (seam.Direction == Direction.Vertical)
                {
                    checkSeam = verticalSeams[index];
                }
                else if (seam.Direction == Direction.Horizontal)
                {
                    checkSeam = horizontalSeams[index];
                }
                fromPixelCheck = index - checkSeam.FromPixel;
                toPixelCheck = index + checkSeam.ToPixel;
                // Does it interfere with the affected region?
                if (!(toPixelCheck < fromPixel || fromPixelCheck > toPixel))
                {
                    if (seam.Direction == Direction.Vertical)
                        verticalSeams[index] = ComputeSeam(Direction.Vertical,
                        index, EnergyFunction, EnergyBias, size);
                    else if (seam.Direction == Direction.Horizontal)
                        horizontalSeams[index] = ComputeSeam(Direction.
                        Horizontal, index, EnergyFunction, EnergyBias,
                        size);
                    recomputedSeamCount++;
                }
            }
        }

        /// <summary>
        /// Compute an individual seam
        /// </summary>
        /// <param name="direction">The direction.</param>
        /// <param name="index">The index.</param>
        /// <param name="energyFunction">The energy function.</param>
        /// <param name="energyBias">The energy bias.</param>
        /// <param name="size">The size.</param>
        /// <returns></returns>
        private Seam ComputeSeam(Direction direction, int index, EnergyFunction energyFunction, EnergyBias energyBias, Size size)
        {
            int totalDiff = 0;
            int totalEnergy = 0;
            int lowestEnergy = 0, e0 = 0, e1 = 0, e2 = 0, b0 = 0, b1 = 0, b2 = 0, d0 = 0, d1 =0, d2 = 0, lowestDiff = 0;
            Seam seam = new Seam();
            SeamPixel seamPixel;
            int lowestPixel = 0, highestPixel = 0;
            int x = 0, y = 0;
            int xInc = 0, yInc = 0; // x and y increments
            int pixelCount = 0; // Maximum number of pixels
            int pixelIndex = 0;
            // Set seam values
            seam.Direction = direction;
            seam.Pixel = index;

            // Set initial values
            if (direction == Direction.Vertical)
            {
                x = index;
                yInc = 1;
                pixelCount = size.Height;
                lowestPixel = x;
                highestPixel = x;
            }
            else if (direction == Direction.Horizontal)
            {
                y = index;
                xInc = 1;
                pixelCount = size.Width;
                lowestPixel = y;
                highestPixel = y;
            }

            // Begin chain of seam pixels
            seam.SeamPixels = new SeamPixel[pixelCount];
            // Get energy at first pixel in seam
            lowestEnergy = energyFunction[x, y, size];
            // Store first pixel
            seamPixel = new SeamPixel();
            seamPixel.PixelDiff = 0;
            seam.SeamPixels[pixelIndex] = seamPixel;
            pixelIndex++;

            // Set totals
            totalEnergy = lowestEnergy;
            totalDiff = 0;

            // Iterate through seam finding lowest energy delta on the way
            x += xInc;
            y += yInc;
            while (x < size.Width && y < size.Height)
            {
                // TODO: Not sure that biases are being used correctly...should check this
                if (direction == Direction.Vertical)
                {
                    b0 = energyBias[x - 1, y, size];
                    b1 = energyBias[x, y, size];
                    b2 = energyBias[x + 1, y, size];
                    e0 = energyFunction[x - 1, y, size] + b0;
                    e1 = energyFunction[x, y, size] + b1;
                    e2 = energyFunction[x + 1, y, size] + b2;
                }
                else if (direction == Direction.Horizontal)
                {
                    b0 = energyBias[x, y - 1, size];
                    b1 = energyBias[x, y, size];
                    b2 = energyBias[x, y + 1, size];
                    e0 = energyFunction[x, y - 1, size] + b0;
                    e1 = energyFunction[x, y, size] + b1;
                    e2 = energyFunction[x, y + 1, size] + b2;
                }

                // Would flipping a sign bit be faster?
                d0 = Math.Abs(lowestEnergy - e0) + b0;
                d1 = Math.Abs(lowestEnergy - e1) + b1;
                d2 = Math.Abs(lowestEnergy - e2) + b2;

                // Create a new seam pixel
                seamPixel = new SeamPixel();

                if (d0 < d1)
                {
                    if (d0 < d2)
                    {
                        if (direction == Direction.Vertical)
                            if (--x < 0) x = 0;
                            else seamPixel.Right = true;
                        else

                            if (--y < 0) y = 0;
                            else seamPixel.Left = true;
                        lowestEnergy = e0;
                        lowestDiff = d0;
                    }
                    else
                    {
                        if (direction == Direction.Vertical)
                            if (++x > size.Width - 1) x = size.Width - 1;
                            else seamPixel.Left = true;
                        else
                            if (++y > size.Height - 1) y = size.Height - 1;
                            else seamPixel.Right = true;
                        lowestEnergy = e2;
                        lowestDiff = d2;
                    }
                }
                else
                {
                    if (d1 <= d2)
                    {
                        lowestEnergy = e1;
                        lowestDiff = d1;
                    }
                    else
                    {
                        if (direction == Direction.Vertical)
                            if (++x > size.Width - 1) x = size.Width - 1;
                            else seamPixel.Left = true;
                        else
                            if (++y > size.Height - 1) y = size.Height - 1;
                            else seamPixel.Right = true;
                        lowestEnergy = e2;
                        lowestDiff = d2;
                    }
                }

                // Track energy difference for this pixel
                seamPixel.PixelDiff = lowestDiff;
                // Add seam pixel to the collection
                seam.SeamPixels[pixelIndex] = seamPixel;
                pixelIndex++;
                // Keep track of totals
                totalEnergy += lowestEnergy;
                totalDiff += lowestDiff;

                if (direction == Direction.Vertical)
                {
                    // Is this the new leftmost pixel?
                    if (x < lowestPixel) lowestPixel = x;
                    // Is this the new rightmost pixel?
                    if (x > highestPixel) highestPixel = x;
                }
                else
                {
                    // Is this the new leftmost pixel?
                    if (y < lowestPixel) lowestPixel = y;

                    // Is this the new rightmost pixel?
                    if (y > highestPixel) highestPixel = y;
                }
                // Next pixel
                x += xInc;
                y += yInc;
            }

            // Track information about this seam
            seam.TotalEnergy = totalEnergy;
            seam.TotalDiff = totalDiff;
            seam.FromPixel = index - lowestPixel;
            seam.ToPixel = highestPixel - index;
            // Return the seam
            return seam;
        }

        /// <summary>
        /// Find the lowest energy seam in the array
        /// Since we’re sometimes comparing horizontal to vertical, we need to compute based on an average
        /// </summary>
        /// <param name="direction">The direction.</param>
        /// <param name="comparisonMethod">The comparison method.</param>
        /// <param name="size">The size.</param>
        /// <returns></returns>
        public override Seam FindLowestEnergy(Direction direction, ComparisonMethod comparisonMethod, Size size)
        {
            int lowestEnergyIndex = 0;
            double lowestEnergy = double.MaxValue;
            double energyCompare = 0; // The energy value to compare to lowest
            int maxIndex = 0;
            Seam[] seams = null;
            if (direction == Direction.Vertical)
            {
                seams = verticalSeams;
                maxIndex = size.Width;
            }
            else if (direction == Direction.Horizontal)
            {
                seams = horizontalSeams;
                maxIndex = size.Height;
            }

            // Find the lowest energy seam
            for (int index = 0; index < maxIndex; index++)
            {
                if (comparisonMethod == ComparisonMethod.Total)
                {
                    // Compare simple totals for each seam
                    energyCompare = seams[index].TotalDiff;
                }
                else if (comparisonMethod == ComparisonMethod.Average)
                {
                    // Compare the average energy value for each seam (depends on seam direction)
                    if (seams[index].Direction == Direction.Vertical)
                        energyCompare = seams[index].TotalDiff / size.Height;
                    else if (seams[index].Direction == Direction.Horizontal)
                        energyCompare = seams[index].TotalDiff / size.Width;

                }
                else if (comparisonMethod == ComparisonMethod.DiffBias)
                {
                    // TODO: Experimental computation...not sure how well it really works
                    if (seams[index].Direction == Direction.Vertical)
                        energyCompare = seams[index].TotalEnergy / size.Height;
                    else if (seams[index].Direction == Direction.Horizontal)
                        energyCompare = seams[index].TotalEnergy / size.Width;
                    energyCompare += seams[index].TotalDiff;
                }

                // Is this the lowest energy seam so far?
                if (energyCompare < lowestEnergy)
                {
                    lowestEnergy = energyCompare;
                    lowestEnergyIndex = index;
                }

            }
            seams[lowestEnergyIndex].CompareValue = lowestEnergy; // Set value to compare (since it won’t always be simple)
            return seams[lowestEnergyIndex];
        }

        /// <summary>
        /// Updates the seam energy.
        /// </summary>
        /// <param name="direction">The direction.</param>
        /// <param name="x">The x.</param>
        /// <param name="y">The y.</param>
        public override void UpdateSeamEnergy(Direction direction, int x, int y)
        {
            if (direction == Direction.Vertical)
            {
                // If we remove a pixel in the vertical direction, we need to subtract the energy values from the horizontal sums
                horizontalSeams[y].TotalEnergy -= EnergyFunction.EnergyMap[x, y];
                if (y > 0)
                    horizontalSeams[y].TotalDiff -= verticalSeams[x].
                SeamPixels[y - 1].PixelDiff;
            }
            else if (direction == Direction.Horizontal)
            {
                // If we remove a pixel in the horizontal direction, we need to subtract the energy values from the vertical sums
                verticalSeams[x].TotalEnergy -= EnergyFunction.EnergyMap[x, y];
                if (x > 0)
                    verticalSeams[x].TotalDiff -= horizontalSeams[y].
                SeamPixels[x - 1].PixelDiff;
            }
        }
    }
}


